package sve;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import sve.FactorHandler.Factor;

public class FactorHandler {
	public static class Factor {
		String var;
		int id;
		ArrayList<String> factorVars = new ArrayList<String>();

		public String toString() {
			return this.var + " > " + this.factorVars.toString();
		}

		public static ArrayList<String> getVars(List<Factor> hs) {
			ArrayList<String> s = new ArrayList<String>();
			for (Factor f : hs) {
				s.add(f.var);
			}
			return s;
		}

	}

	private Buffer _buffer;
	private ArrayList<Factor> _fset;

	private FactorHandler(Buffer b) {
		_buffer = b;
		_fset = new ArrayList<FactorHandler.Factor>();
	}

	public static FactorHandler newInstance(Buffer b) {
		FactorHandler f = new FactorHandler(b);
		ArrayList<String> s = f.getSortedVars(false);
		f._fset = new ArrayList<Factor>();
		for (String var : s) {
			Set<String> parents = f._buffer.getContext().collectVars(f._buffer.getVar(var));
			if (find(f, var, parents) == null) {
				add(f, var, f._buffer.getVar(var), parents);
			}

		}

		return f;
	}

	private static void add(FactorHandler fh, String var, int id, Set<String> parent) {
		Factor ff = new Factor();
		ff.var = var;
		ff.id = id;
		for (String p : parent) {
			ff.factorVars.add(p);
		}
		fh._fset.add(ff);
	}

	// gets factrs that refer to this variable
	public ArrayList<Factor> getReferenceFactors(String var) {
		ArrayList<Factor> result = new ArrayList<FactorHandler.Factor>();

		for (Factor f : this._fset) {
			if (f.factorVars.contains(var)) {
				result.add(f);
			}
		}

		return result;
	}

	public ArrayList<Factor> getFactors(String var) {
		ArrayList<Factor> fh = new ArrayList<FactorHandler.Factor>();
		for (Factor f : _fset) {
			if (f.var.equals(var)) {
				fh.add(f);
			}
		}

		return fh;
	}

	public Factor find(String var) {
		return find(this, var, _buffer.getContext().collectVars(_buffer.getVar(var)));
	}

	public static Factor find(FactorHandler fh, String var, Set<String> parent) {

		for (Factor f : fh._fset) {
			if (f.factorVars.size() == parent.size() && f.var.equals(var)) { // they have equal sizes
				boolean found = true;
				for (String s : parent) {
					if (!f.factorVars.contains(s)) {
						found = false;
						break;
					}
				}
				if (found)
					return f;
			}
		}

		return null;
	}

	public ArrayList<String> collectVars() {
		ArrayList<String> ar = new ArrayList<String>();
		for (Factor f : _fset) {
			if (!ar.contains(f.var)) {
				ar.add(f.var);
			}
		}
		return ar;
	}

	public ArrayList<String> getSortedVars(boolean reverse) {
		ArrayList<String> s = new ArrayList<String>();
		for (String var : _buffer.getAllVars())
			// _buffer.getVarIds().keySet())
			insertVars(var, s);

		if (reverse) {
			for (int i = 0; i < s.size() / 2; i++) {
				String tmp = s.get(i);
				s.set(i, s.get(s.size() - 1 - i));
				s.set(s.size() - 1 - i, tmp);
			}
		}

		return s;
	}

	private void insertVars(String var, ArrayList<String> s) {
		int id = -1;
		if (!s.contains(var) && (id = _buffer.getVarId(var)) > 0) {
			for (String v : _buffer.getContext().collectVars(id)) {
				if (!v.equals(var)) {
					insertVars(v, s);
				}
			}
		}
		if (!s.contains(var))
			s.add(var);
	}

	public static void remove(FactorHandler all_factors, int id) {
		Buffer b = all_factors._buffer;
		String var = b.getVar(id);
		all_factors._fset.remove(find(all_factors, var, b.getContext().collectVars(b.getVar(var))));
	}

	public static void add(FactorHandler all_factors, int id) {
		Buffer b = all_factors._buffer;
		String var = b.getVar(id);
		add(all_factors, var, id, b.getContext().collectVars(id));
	}

	// removes all its children
	public void remove(int id) {
		remove(this, id);
	}

	public ArrayList<Factor> getFactors() {
		return _fset;
	}

	public void update(String var, int current_id) {
		Factor ff = getSingleFactor(var);
		if (ff != null && ff.id == current_id)
			return;

		ArrayList<Factor> remove = new ArrayList<FactorHandler.Factor>();
		for (Factor f : getFactors()) {
			if (f.var.equals(var) || f.factorVars.contains(var)) { //
				remove.add(f);
			}
		}
		this._fset.removeAll(remove);
		ff = getSingleFactor(Integer.toString(current_id));
		if (ff == null)
			add(this, Integer.toString(current_id), current_id); // var
	}

	private static void add(FactorHandler fh, String var, int id) {
		Buffer b = fh._buffer;
		add(fh, var, id, b.getContext().collectVars(id));
	}

	public void remove(Factor f) {
		_fset.remove(f);
	}

	public void remove(String var1, String var2) {
		ArrayList<Factor> ar = new ArrayList<Factor>();

		for (Factor f : _fset) {
			if (f.factorVars.contains(var1) && f.factorVars.contains(var2)) {
				ar.add(f);
			}
		}
		_fset.remove(ar);
	}

	public String toString() {
		String s = "";
		for (Factor f : _fset) {
			s = s + f.toString() + ", ";
		}

		return s;
	}

	public Factor get(String var1, String var2) {
		for (Factor f : _fset) {
			if (f.factorVars.contains(var1) && f.factorVars.contains(var2)
					|| (f.var.equals(var1) && f.factorVars.contains(var2))
					|| (f.var.equals(var2) && f.factorVars.contains(var1))) {
				return (f);
			}
		}

		return null;
	}

	public Factor getSingleFactor(String var) {
		for (Factor f : _fset) {
			if (f.var.equals(var)) {
				return f;
			}
		}
		return null;
	}

	public ArrayList<Factor> getParentChild(String var) {
		ArrayList<Factor> a = new ArrayList<FactorHandler.Factor>(getReferenceFactors(var));
		Utils.addAll(a, collectVars(var));
		return a;
	}

	public ArrayList<Factor> collectVars(String var) {
		ArrayList<Factor> a = new ArrayList<FactorHandler.Factor>();
		int id = _buffer.getVar(var);
		if (id > 0) {
			for (String s : _buffer.getContext().collectVars(id)) {
				a.add(getSingleFactor(s));
			}
		}
		return a;
	}

	public ArrayList<Factor> getSingleFactors(ArrayList<String> factorVars) {
		ArrayList<Factor> a = new ArrayList<FactorHandler.Factor>();
		for (String v : factorVars) {
			a.add(getSingleFactor(v));
		}
		return a;
	}

	public void clearFactors() {
		_fset.clear();
	}

	public void addAll(ArrayList<Factor> factor_col) {
		_fset.addAll(factor_col);
	}
}
